package com.isyscore.os.metadata.service.impl;

import cn.hutool.core.date.DateUtil;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.isyscore.boot.login.cache.LoginUserHolder;
import com.isyscore.os.core.exception.DataFactoryException;
import com.isyscore.os.metadata.database.AbstractDatabase;
import com.isyscore.os.metadata.enums.MetricJobTaskStatus;
import com.isyscore.os.metadata.enums.TimePeriodUnit;
import com.isyscore.os.metadata.manager.DatabaseManager;
import com.isyscore.os.metadata.model.dto.DataSourceDTO;
import com.isyscore.os.metadata.model.dto.MetricDimFilterDTO;
import com.isyscore.os.metadata.model.dto.QuerySqlResultDTO;
import com.isyscore.os.metadata.model.dto.TimePeriodDTO;
import com.isyscore.os.metadata.model.entity.*;
import com.isyscore.os.metadata.model.vo.DataQueryVO;
import com.isyscore.os.metadata.service.DataBuildService;
import com.isyscore.os.metadata.utils.TimePeriodUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.Expression;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.isyscore.os.core.exception.ErrorCode.METRIC_CALCULATE_ERROR;
import static com.isyscore.os.metadata.constant.CommonConstant.*;


@Service
@Slf4j
public class MetricCalculateService {

    @Autowired
    private MetricService metricService;

    @Autowired
    private SqlStatementService sqlStatementService;

    @Autowired
    private MetricDimFilterService metricDimFilterService;

    @Autowired
    private DatamodelService datamodelService;

    @Autowired
    private FormulaService formulaService;

    @Autowired
    private MetricJobBatchService metricJobBatchService;

    @Autowired
    private MetricJobBatchResultService metricJobBatchResultService;

    @Autowired
    private DataSourceServiceImpl dataSourceService;

    @Autowired
    private DataBuildService dataBuildService;


    public List<MetricJobBatchResult> testMertricJob(Long metricId) {
        Metric metric = metricService.getFullMetricInfoById(metricId);
        DataQueryVO sqlExecuteReq = parseMetricSqlExecuteReq(metric, 20, null, null);
        return this.executeMetricSqlSync(sqlExecuteReq, metric, null);
    }

    public void calculateMetricWithLog(Long metricId, TimePeriodDTO timePeriod, List<MetricDimFilterDTO> dynamicFilters) {
        MetricJobBatch metricJobBatch = new MetricJobBatch();
        metricJobBatch.setMetricRefId(metricId);
        metricJobBatch.setStartTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        metricJobBatch.setStatus(MetricJobTaskStatus.running.getStatus());
        metricJobBatch.setType(1);
        try {
            Metric metric = metricService.getFullMetricInfoById(metricId);
            if (timePeriod == null) {
                timePeriod = getTimePeriodByMetric(metric);
            }
            DataQueryVO sqlExecuteReq = parseMetricSqlExecuteReq(metric, MAX_METRIC_DATA_RESULT_LIMIT, timePeriod, dynamicFilters);
            metricJobBatch.setDataPeriod(timePeriod.getKey());
            metricJobBatch.setExecutedSql(sqlExecuteReq.getSqlText());
            metricJobBatch.setMetricName(metric.getName());
            List<MetricJobBatchResult> jobBatchResults = executeMetricSqlSync(sqlExecuteReq, metric, timePeriod);
            if (jobBatchResults != null) {
//                metricJobBatchResultService.removeResultsByMetricAndPeriod(metricJobBatch.getMetricRefId(), timePeriod.getKey());
                if (!jobBatchResults.isEmpty()) {
                    metricJobBatchResultService.saveBatch(jobBatchResults);
                }
                metricJobBatch.setResultQuantity(jobBatchResults.size());
            }
            metricJobBatch.setStatus(MetricJobTaskStatus.success.getStatus());
        } catch (Exception e) {
            log.error("指标计算定时任务发生异常，ID为:" + metricId, e);
            metricJobBatch.setStatus(MetricJobTaskStatus.fail.getStatus());
            metricJobBatch.setMessage(DataFactoryException.getMessage(e));
            throw new DataFactoryException(METRIC_CALCULATE_ERROR);
        }finally {
            metricJobBatch.setEndTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            metricJobBatchService.save(metricJobBatch);
        }

    }

    /**
     * 同步执行SQL并获取结果
     */
    public List<MetricJobBatchResult> executeMetricSqlSync(DataQueryVO sqlExecuteReq, Metric metricWithAllProps, TimePeriodDTO timePeriod) {
        QuerySqlResultDTO queryResDTO = dataBuildService.executeQuerySqlSync(sqlExecuteReq);
        return parseMetricResultWithSqlResults(metricWithAllProps, timePeriod, queryResDTO.getResults());
    }

    public DataQueryVO parseMetricSqlExecuteReq(Metric metric, Integer maxResultsLimit, TimePeriodDTO timePeriod, List<MetricDimFilterDTO> dynamicFilters) {
        Datamodel datamodel = datamodelService.getDatamodelById(metric.getDatamodelRefId());
        String metricSql = sqlStatementService.getSqlById(metric.getSqlRefId());
        if (timePeriod == null) {
            timePeriod = getTimePeriodByMetric(metric);
        }
        DataSourceDTO dataSource = dataSourceService.getDataSource(datamodel.getDsRefId());
        AbstractDatabase db = DatabaseManager.findDb(dataSource);
        //如果是默认的时间期key说明不存在时间范围条件，则无需替换动态时间参数
        if (!DEFAULT_METRIC_DATE_PERIOD_KEY.equalsIgnoreCase(timePeriod.getKey())) {
            String startTime = DateUtil.format(timePeriod.getStartTime(), "yyyy-MM-dd HH:mm:ss");
            String endTime = DateUtil.format(timePeriod.getEndTime(), "yyyy-MM-dd HH:mm:ss");
            //替换时间维度的动态参数
            metricSql = metricSql.replace(METRIC_TIME_PERIOD_START_TIME_PARAMNAME, db.getParseStr2DateEl("'"+startTime+"'"));
            metricSql = metricSql.replace(METRIC_TIME_PERIOD_END_TIME_PARAMNAME, db.getParseStr2DateEl("'"+endTime+"'"));
        }
        metricSql = parseDynamicalFilters(metricSql, metric, dynamicFilters, db);
        DataQueryVO sqlExecuteReq = new DataQueryVO();
        sqlExecuteReq.setSqlText(metricSql);
        sqlExecuteReq.setDataSourceId(datamodel.getDsRefId());
        sqlExecuteReq.setDatabaseName(datamodel.getDbName());
        sqlExecuteReq.setSize(maxResultsLimit);
        sqlExecuteReq.setCurrent(1);
        sqlExecuteReq.setSearchCount(false);
        return sqlExecuteReq;
    }


    private List<MetricJobBatchResult> parseMetricResultWithSqlResults(Metric metricWithAllProps, TimePeriodDTO timePeriod, List<Map<String, Object>> sqlResults) {
        List<MetricMeasure> measures = metricWithAllProps.getMeasures();
        if (timePeriod == null) {
            timePeriod = getTimePeriodByMetric(metricWithAllProps);
        }
        //预先编译公式
        Expression formulaExpr = null;
        if (!Strings.isNullOrEmpty(metricWithAllProps.getFormula())) {
            List<String> measureNames = measures.stream().map(MetricMeasure::getMeasureColName).collect(Collectors.toList());
            formulaExpr = formulaService.compileFormula(metricWithAllProps.getFormula(), measureNames);
        }
        Expression finalFormulaExpr = formulaExpr;
        //生成计算结果的时间期的Key
        List<MetricJobBatchResult> jobBatchResults = Lists.newArrayList();
        for (Map<String, Object> sqlResult : sqlResults) {
            MetricJobBatchResult metricRow = new MetricJobBatchResult();
            metricRow.setCreateTime(LocalDateTime.now());
            metricRow.setTenantId(LoginUserHolder.getTenantId());
            metricRow.setMetricRefId(metricWithAllProps.getId());
            if (!Strings.isNullOrEmpty(metricWithAllProps.getDimension())) {
                Object dimVal = sqlResult.get(metricWithAllProps.getDimension());
                if (dimVal != null) {
                    metricRow.setDim(dimVal.toString());
                }
            }
            metricRow.setDataPeriod(timePeriod.getKey());
            if (finalFormulaExpr == null) {
                Object measureObj = sqlResult.get(measures.get(0).getMeasureColName());
                if (measureObj != null) {
                    metricRow.setMeasure(measureObj.toString());
                }
            } else {
                Map<String, Object> formulaParams = Maps.newHashMap();
                for (MetricMeasure measure : measures) {
                    formulaParams.put(measure.getMeasureColName(), sqlResult.get(measure.getMeasureColName()));
                }
                String metricVal = formulaService.doCalulateFormula(finalFormulaExpr, formulaParams);
                metricRow.setMeasure(metricVal);
            }
            jobBatchResults.add(metricRow);
        }
        return jobBatchResults;
    }


    private String parseDynamicalFilters(String metricSql, Metric metric, List<MetricDimFilterDTO> dynamicFilters, AbstractDatabase dbHelper) {
        //处理指标计算的动态条件
        if (dynamicFilters != null && !dynamicFilters.isEmpty()) {
            List<MetricDimFilterDTO> fixedFilters = metricDimFilterService.getFilterByMetricId(metric.getId());
            Set<String> fixedFilterColumns = fixedFilters.stream().map(MetricDimFilterDTO::getDimColName).collect(Collectors.toSet());
            StringBuilder dynamicFilterSql = new StringBuilder();
            for (MetricDimFilterDTO filter : dynamicFilters) {
                if (!fixedFilterColumns.contains(filter.getDimColName())) {
                    String filterSql = metricDimFilterService.parseFilter2Sql(dbHelper, filter);
                    dynamicFilterSql.append(" AND ");
                    dynamicFilterSql.append(filterSql);
                }
            }
            metricSql = metricSql.replace(METRIC_SQL_DYNAMIC_FILTERS, dynamicFilterSql.toString());
        } else {
            metricSql = metricSql.replace(METRIC_SQL_DYNAMIC_FILTERS, "");
        }
        return metricSql;
    }

    private TimePeriodDTO getTimePeriodByMetric(Metric metric) {
        TimePeriodDTO timePeriod = null;
        if (!Strings.isNullOrEmpty(metric.getTimePeriodDim())) {
            timePeriod = TimePeriodUtil.getTimePeriodByTimePoint(new Date(), TimePeriodUnit.valueOf(metric.getTimePeriodUnit()), metric.getTimePeriodNumber());
        } else {
            timePeriod = new TimePeriodDTO();
            timePeriod.setKey(DEFAULT_METRIC_DATE_PERIOD_KEY);
        }
        return timePeriod;
    }


}
